package org.terramagnet.mvcframework.tools;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.WebUtils;

/**
 * Spring MVC 上下文工具类.
 *
 * @author lip
 */
public class SpringMvcUtils {

    public static final String SESSION_REFERER = SpringMvcUtils.class.getName() + "@S-referer";

    /**
     * 获取当前Session中对象. 若当前无{@code session}则返回{@code null}且不会创建{@code session}.
     *
     * @param <T> 保存的对象类型.
     * @param key 对象保存键值.
     * @return 保存的对象或{@code null}.
     * @throws IllegalStateException 当前线程不是web请求抛出此异常.
     */
    public static <T> T getSessionAttribute(String key) throws IllegalStateException {
        return (T) WebUtils.getSessionAttribute(currentRequest(), key);
    }

    /**
     * 获取当前Session中对象. 若当前无{@code session}则创建一个.
     *
     * @param <T> 保存的对象类型.
     * @param key 对象保存键值.
     * @param clazz 保存的对象类型.
     * @return 保存的对象.
     * @throws IllegalStateException 当前线程不是web请求抛出此异常.
     */
    public static <T> T getOrCreateSessionAttribute(String key, Class<T> clazz) throws IllegalStateException {
        HttpSession session = currentRequest().getSession();
        return (T) WebUtils.getOrCreateSessionAttribute(session, key, clazz);
    }

    /**
     * 保存变量到当前Session. 若保存的对象为{@code null}，则移除该键值所保存的对象。
     *
     * @param key 键值.
     * @param obj 要保存的对象或{@code null}.
     * @throws IllegalStateException 当前线程不是web请求抛出此异常.
     */
    public static void setSessionAttribute(String key, Object obj) throws IllegalStateException {
        WebUtils.setSessionAttribute(currentRequest(), key, obj);
    }

    /**
     * 保存当前Referer信息.
     *
     * <p>
     * 请求中不包含{@code Referer}信息时，返回当前请求的URL（包含ContextPath部分）。</p>
     *
     * @throws IllegalStateException 当前线程不是web请求时抛出此异常.
     */
    public static void saveReferer() throws IllegalStateException {
        String referer = null;
        HttpServletRequest request = currentRequest();
        for (Cookie cookie : request.getCookies()) {
            if ("Referer".equalsIgnoreCase(cookie.getName())) {
                referer = cookie.getValue();
            }
        }
        if (referer == null) {
            referer = request.getRequestURI();
            String query = request.getQueryString();
            if (StringUtils.isNotBlank(query)) {
                referer += "?" + query;
            }
        }
        setSessionAttribute(SESSION_REFERER, referer);
    }

    /**
     * 获取最后一次保存的Referer信息或{@code null}.
     */
    public static String getLastReferer() {
        return getSessionAttribute(SESSION_REFERER);
    }

    /**
     * 获取当前Request对象.
     *
     * @return 当前Request对象或{@code null}.
     * @throws IllegalStateException 当前线程不是web请求抛出此异常.
     */
    public static HttpServletRequest currentRequest() throws IllegalStateException {
        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attrs == null) {
            throw new IllegalStateException("当前线程中不存在 Request 上下文");
        }
        return attrs.getRequest();
    }

    /**
     * 获取当前session对象. 若当前线程不是web请求或当前尚未创建{@code session}则返回{@code null}.
     *
     * @return 当前session对象或{@code null}.
     */
    public static HttpSession currentSession() {
        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attrs == null) {
            return null;
        }
        return attrs.getRequest().getSession(false);
    }

    /**
     * 获取当前请求的url.
     * <strong>不包含</strong>{@code ContextPath}和查询字符串（{@code ?}及其之后的部分）
     *
     * @return 请求的URL
     */
    public static String currentRequestUrl() {
        HttpServletRequest request = currentRequest();
        String requestURI = request.getRequestURI();
        return requestURI.substring(request.getContextPath().length());
    }

    private static String currentHostCache;

    /**
     * 获取当前主机前缀. 不包含{@code ContextPath}，不包含末尾'{@code /}'，例如：
     * {@code http://web.etuo.cn:8080}
     */
    public static String currentHost() {
        if (currentHostCache == null) {
            HttpServletRequest request = currentRequest();
            StringBuilder builder = new StringBuilder();
            builder.append(request.getScheme())
                .append("://")
                .append(request.getServerName());
            if (!isSchemeDefaultPort(request.getScheme(), request.getServerPort())) {
                builder.append(":")
                    .append(request.getServerPort());
            }
            currentHostCache = builder.toString();
        }
        return currentHostCache;
    }

    /**
     * 获取访问者IP地址.
     */
    public static String currentRemoteIp() {
        HttpServletRequest request = currentRequest();
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
    }

    /**
     * 获取当前Spring应用上下文.
     */
    public static ApplicationContext currentApplicationContext() {
        return ContextLoader.getCurrentWebApplicationContext();
    }

    /**
     * 获取当前J2EE环境的{@code ServletContext}.
     */
    public static ServletContext currentServletContext() {
        WebApplicationContext currentWebApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        if (currentWebApplicationContext == null) {
            return null;
        }
        return currentWebApplicationContext.getServletContext();
    }

    /**
     * 获取当前J2EE环境的{@code ContextPath}.
     */
    public static String currentContextPath() {
        return currentServletContext().getContextPath();
    }

    private static boolean isSchemeDefaultPort(String scheme, int port) {
        switch (port) {
            case 80:
                return scheme.equalsIgnoreCase("http");
            case 443:
                return scheme.equalsIgnoreCase("https");
            case 21:
                return scheme.equalsIgnoreCase("ftp");
        }
        return false;
    }
    private static final Pattern REGEXP_TEMPLATE_VARIABLES = Pattern.compile("\\$\\{((?!\\d)[0-9a-zA-Z]+)\\}", Pattern.CASE_INSENSITIVE);

    /**
     * 替换模板中的变量. 变量是指{@code ${..}}形式的待替换文本。
     *
     * <p>
     * 若模板中不包含变量，原样返回模板文本。</p>
     *
     * <p>
     * 变量值从{@link WebApplicationContext#getEnvironment() Environment}中查找。找不到值的变量不被替换</p>
     *
     * @param template 模板文本
     * @return 替换后的文本
     */
    public static StringBuilder replaceTemplateVariables(String template) {
        Matcher matcher = REGEXP_TEMPLATE_VARIABLES.matcher(template);
        WebApplicationContext ctx = ContextLoader.getCurrentWebApplicationContext();
        Environment environment = ctx.getEnvironment();

        int prev = 0;
        StringBuilder text = new StringBuilder();
        while (matcher.find()) {
            String variableName = matcher.group(1);
            String variableValue = environment.getProperty(variableName);
            if (variableValue != null) {
                text.append(template.substring(prev, matcher.start()))
                    .append(variableValue);
                prev = matcher.end();
            }
        }
        if (prev < template.length()) {
            text.append(template.substring(prev));
        }
        return text;
    }
}
